package com.vinjune.bigdata.bloomfilter.tool;

import java.io.Serializable;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.BitSet;
import java.util.Random;
import java.util.function.BiFunction;
import java.util.zip.Adler32;
import java.util.zip.CRC32;
import java.util.zip.Checksum;

/**
 * Created by 000376 on 2018/3/7.
 */
public class HashProvider {
    private static final int seed32 = 89478583;

    public HashProvider() {
    }

    static int hashBytes(byte[] a) {
        long FNV_PRIME = 16777619L;
        long FNV_OFFSET_BASIS = 2166136261L;
        if(a == null) {
            return 0;
        } else {
            long result = 2166136261L;
            byte[] var7 = a;
            int var8 = a.length;

            for(int var9 = 0; var9 < var8; ++var9) {
                byte element = var7[var9];
                result = result * 16777619L & -1L;
                result ^= (long)element;
            }

            return (int)result;
        }
    }

    public static int[] hashCarterWegman(byte[] value, int m, int k) {
        int[] positions = new int[k];
        BigInteger prime32 = BigInteger.valueOf(4294967279L);
        BigInteger prime64 = BigInteger.valueOf(53200200938189L);
        new BigInteger("21213943449988109084994671");
        Random r = new Random(89478583L);
        BigInteger v = new BigInteger(value.length > 0?value:new byte[1]);

        for(int i = 0; i < k; ++i) {
            BigInteger a = BigInteger.valueOf(r.nextLong());
            BigInteger b = BigInteger.valueOf(r.nextLong());
            positions[i] = a.multiply(v).add(b).mod(prime64).mod(BigInteger.valueOf((long)m)).intValue();
        }

        return positions;
    }

    public static int[] hashRNG(byte[] value, int m, int k) {
        int[] positions = new int[k];
        Random r = new Random((long)hashBytes(value));

        for(int i = 0; i < k; ++i) {
            positions[i] = r.nextInt(m);
        }

        return positions;
    }

    public static int[] hashCRC(byte[] value, int m, int k) {
        return hashChecksum(value, new CRC32(), m, k);
    }

    public static int[] hashAdler(byte[] value, int m, int k) {
        return hashChecksum(value, new Adler32(), m, k);
    }

    public static int[] hashChecksum(byte[] value, Checksum cs, int m, int k) {
        int[] positions = new int[k];
        int hashes = 0;
        int salt = 0;

        while(hashes < k) {
            cs.reset();
            cs.update(value, 0, value.length);
            cs.update(hashes + salt++ + 89478583);
            int hash = rejectionSample((int)cs.getValue(), m);
            if(hash != -1) {
                positions[hashes++] = hash;
            }
        }

        return positions;
    }

    public static int[] hashSimpleLCG(byte[] value, int m, int k) {
        long multiplier = 25214903917L;
        long addend = 11L;
        long mask = 281474976710655L;
        int reduced = Math.abs(hashBytes(value));
        if(reduced == -2147483648) {
            reduced = 42;
        }

        int[] positions = new int[k];
        long seed = (long)reduced;

        for(int i = 0; i < k; ++i) {
            seed = seed * 25214903917L + 11L & 281474976710655L;
            positions[i] = (int)(seed >>> 18) % m;
        }

        return positions;
    }

    public static int[] hashMurmur3(byte[] value, int m, int k) {
        return rejectionSample(HashProvider::murmur3_signed, value, m, k);
    }

    public static int[] hashCassandra(byte[] value, int m, int k) {
        int[] result = new int[k];
        long hash1 = murmur3(0, value);
        long hash2 = murmur3((int)hash1, value);

        for(int i = 0; i < k; ++i) {
            result[i] = (int)((hash1 + (long)i * hash2) % (long)m);
        }

        return result;
    }

    public static int murmur3_signed(int seed, byte[] bytes) {
        return (int)murmur3(seed, bytes);
    }

    public static long murmur3(int seed, byte[] bytes) {
        int h1 = seed;
        int c1 = -862048943;
        int c2 = 461845907;
        int len = bytes.length;

        int i;
        int k1;
        for(i = 0; len >= 4; i += 4) {
            k1 = bytes[i + 0] & 255;
            k1 |= (bytes[i + 1] & 255) << 8;
            k1 |= (bytes[i + 2] & 255) << 16;
            k1 |= (bytes[i + 3] & 255) << 24;
            k1 *= c1;
            k1 = Integer.rotateLeft(k1, 15);
            k1 *= c2;
            h1 ^= k1;
            h1 = Integer.rotateLeft(h1, 13);
            h1 = h1 * 5 + -430675100;
            len -= 4;
        }

        if(len > 0) {
            k1 = 0;
            switch(len) {
                case 3:
                    k1 ^= (bytes[i + 2] & 255) << 16;
                case 2:
                    k1 ^= (bytes[i + 1] & 255) << 8;
                case 1:
                    k1 ^= bytes[i] & 255;
                default:
                    k1 *= c1;
                    k1 = Integer.rotateLeft(k1, 15);
                    k1 *= c2;
                    h1 ^= k1;
                    i += len;
            }
        }

        h1 ^= i;
        h1 ^= h1 >>> 16;
        h1 *= -2048144789;
        h1 ^= h1 >>> 13;
        h1 *= -1028477387;
        h1 ^= h1 >>> 16;
        return Integer.toUnsignedLong(h1);
    }

    public static int[] hashMurmur2(byte[] value, int em, int ka) {
        int[] positions = new int[ka];
        int hashes = 0;
        boolean lastHash = false;
        byte[] data = (byte[])value.clone();

        while(hashes < ka) {
            int m;
            for(m = 0; m < value.length; ++m) {
                if(data[m] != 127) {
                    ++data[m];
                    break;
                }

                data[m] = 0;
            }

            m = 1540483477;
            byte r = 24;
            int len = data.length;
            int h = 89478583 ^ len;

            int i;
            for(i = 0; len >= 4; len -= 4) {
                int k = data[i + 0] & 255;
                k |= (data[i + 1] & 255) << 8;
                k |= (data[i + 2] & 255) << 16;
                k |= (data[i + 3] & 255) << 24;
                k *= m;
                k ^= k >>> r;
                k *= m;
                h *= m;
                h ^= k;
                i += 4;
            }

            switch(len) {
                case 3:
                    h ^= (data[i + 2] & 255) << 16;
                case 2:
                    h ^= (data[i + 1] & 255) << 8;
                case 1:
                    h ^= data[i + 0] & 255;
                    h *= m;
                default:
                    h ^= h >>> 13;
                    h *= m;
                    h ^= h >>> 15;
                    int var13 = rejectionSample(h, em);
                    if(var13 != -1) {
                        positions[hashes++] = var13;
                    }
            }
        }

        return positions;
    }

    public static int rejectionSample(int random, int m) {
        random = Math.abs(random);
        return random <= 2147483647 - 2147483647 % m && random != -2147483648?random % m:-1;
    }

    public static int[] rejectionSample(BiFunction<Integer, byte[], Integer> hashFunction, byte[] value, int m, int k) {
        int[] hashes = new int[k];
        int seed = 0;
        int pos = 0;

        while(pos < k) {
            seed = ((Integer)hashFunction.apply(Integer.valueOf(seed), value)).intValue();
            int hash = rejectionSample(seed, m);
            if(hash != -1) {
                hashes[pos++] = hash;
            }
        }

        return hashes;
    }

    public static int[] hashCrypt(byte[] value, int m, int k, String method) {
        MessageDigest cryptHash = null;

        try {
            cryptHash = MessageDigest.getInstance(method);
        } catch (NoSuchAlgorithmException var18) {
            throw new RuntimeException(var18);
        }

        int[] positions = new int[k];
        int computedHashes = 0;
        new Random(89478583L);
        byte[] digest = new byte[0];

        while(computedHashes < k) {
            cryptHash.update(digest);
            digest = cryptHash.digest(value);
            BitSet hashed = BitSet.valueOf(digest);
            int filterSize = 32 - Integer.numberOfLeadingZeros(m);
            int hashBits = digest.length * 8;

            for(int split = 0; split < hashBits / filterSize && computedHashes < k; ++split) {
                int from = split * filterSize;
                int to = (split + 1) * filterSize;
                BitSet hashSlice = hashed.get(from, to);
                long[] longHash = hashSlice.toLongArray();
                int intHash = longHash.length > 0?(int)longHash[0]:0;
                if(intHash < m) {
                    positions[computedHashes] = intHash;
                    ++computedHashes;
                }
            }
        }

        return positions;
    }

    public static enum HashMethod {
        RNG(HashProvider::hashRNG),
        CarterWegman(HashProvider::hashCarterWegman),
        CRC32(HashProvider::hashCRC),
        Adler32(HashProvider::hashAdler),
        Murmur2(HashProvider::hashMurmur2),
        Murmur3(HashProvider::hashMurmur3),
        Murmur3KirschMitzenmacher(HashProvider::hashCassandra),
        FNVWithLCG(HashProvider::hashSimpleLCG),
        MD2((bytes, m, k) -> {
            return HashProvider.hashCrypt(bytes, m, k, "MD2");
        }),
        MD5((bytes, m, k) -> {
            return HashProvider.hashCrypt(bytes, m, k, "MD5");
        }),
        SHA1((bytes, m, k) -> {
            return HashProvider.hashCrypt(bytes, m, k, "SHA-1");
        }),
        SHA256((bytes, m, k) -> {
            return HashProvider.hashCrypt(bytes, m, k, "SHA-256");
        }),
        SHA384((bytes, m, k) -> {
            return HashProvider.hashCrypt(bytes, m, k, "SHA-384");
        }),
        SHA512((bytes, m, k) -> {
            return HashProvider.hashCrypt(bytes, m, k, "SHA-512");
        });

        private HashProvider.HashFunction hashFunction;

        private HashMethod(HashProvider.HashFunction hashFunction) {
            this.hashFunction = hashFunction;
        }

        public HashProvider.HashFunction getHashFunction() {
            return this.hashFunction;
        }
    }

    public interface HashFunction extends Serializable {
        int[] hash(byte[] var1, int var2, int var3);
    }
}

